Android 사용자 정의 잠 금 화면 그림 보기
Android 사용자 정의 View 기능 은 고급 엔지니어 가 되 는 데 필수 적 인 것 입 니 다.필 자 는 사용자 정의 View 는 지름길 이 없고 자주 연습 해 야 제품 수 요 를 해결 할 수 있다 고 생각 합 니 다.필자 도 오랫동안 사용자 정의 View 를 쓰 지 않 았 으 니 빨리 컨트롤 을 써 서 느낌 을 찾 아 보 자.
본 고 는 잠 금 화면 도안 의 사용자 정의 컨트롤 을 실현 한다.효과 도 는 다음 과 같다.
Github 주소:AndroidSample
LockView 소개
사용자 정의 속성:
인용 방법:
(1)레이아웃 파일 에 도입
<com.xing.androidsample.view.LockView
android:id="@+id/lock_view"
app:rowCount="4"
app:normalColor=""
app:moveColor=""
app:errorColor=""
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="40dp" />
(2)코드 에 정확 한 그림 을 설정 하여 일치 하 는 지 확인 하고 리 셋 에서 결 과 를 얻 을 수 있 습 니 다.
List<Integer> intList = new ArrayList<>();
intList.add(3);
intList.add(7);
intList.add(4);
intList.add(2);
lockView.setStandard(intList);
lockView.setOnDrawCompleteListener(new LockView.OnDrawCompleteListener() {
@Override
public void onComplete(boolean isSuccess) {
Toast.makeText(CustomViewActivity.this, isSuccess ? "success" : "fail", Toast.LENGTH_SHORT).show();
}
});
사고의 방향 을 실현 하 다.사용자 정의 속성
res/values 디 렉 터 리 에 새 attrs.xml 파일 을 만 듭 니 다:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LockView">
<attr name="normalColor" format="color|reference" /> <!-- -->
<attr name="moveColor" format="color|reference" /> <!-- -->
<attr name="errorColor" format="color|reference" /> <!-- -->
<attr name="rowCount" format="integer" /> <!-- -->
</declare-styleable>
</resources>
사용자 정의 속성 가 져 오기
public LockView(Context context) {
this(context, null);
}
public LockView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
readAttrs(context, attrs);
init();
}
/**
*
*/
private void readAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LockView);
normalColor = typedArray.getColor(R.styleable.LockView_normalColor, DEFAULT_NORMAL_COLOR);
moveColor = typedArray.getColor(R.styleable.LockView_moveColor, DEFAULT_MOVE_COLOR);
errorColor = typedArray.getColor(R.styleable.LockView_errorColor, DEFAULT_ERROR_COLOR);
rowCount = typedArray.getInteger(R.styleable.LockView_rowCount, DEFAULT_ROW_COUNT);
typedArray.recycle();
}
/**
*
*/
private void init() {
stateSparseArray = new SparseIntArray(rowCount * rowCount);
points = new PointF[rowCount * rowCount];
innerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
innerCirclePaint.setStyle(Paint.Style.FILL);
outerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
outerCirclePaint.setStyle(Paint.Style.FILL);
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setStrokeCap(Paint.Cap.ROUND);
linePaint.setStrokeJoin(Paint.Join.ROUND);
linePaint.setStrokeWidth(30);
linePaint.setColor(moveColor);
}
원 의 반지름 을 계산 하 다외원 반경 과 인접 한 두 원 사이 의 간격 을 설정 하고 내원 반경 은 외원 반경의 절반 이기 때문에 반경 계산 방식 은 다음 과 같다.
radius = Math.min(w, h) / (2 * rowCount + rowCount - 1) * 1.0f;
각 원 좌표 설정각 원 좌 표 는 1 차원 배열 로 저장 하고 계산 방식 은 다음 과 같다.
//
for (int i = 0; i < rowCount * rowCount; i++) {
points[i] = new PointF(0, 0);
points[i].set((i % rowCount * 3 + 1) * radius, (i / rowCount * 3 + 1) * radius);
}
측정 View 너비측정 모드 에 따라 컨트롤 의 너비 와 높이 를 설정 합 니 다.레이아웃 파일 에 wrap 을 설정 합 니 다.content,기본적으로 컨트롤 폭 을 600 dp 로 설정 합 니 다.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getSize(widthMeasureSpec);
int height = getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int getSize(int measureSpec) {
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
return size;
} else if (mode == MeasureSpec.AT_MOST) {
return Math.min(size, dp2Px(600));
}
return dp2Px(600);
}
onTouchEvent()터치 이벤트손가락 이 미 끄 러 지 는 과정 에서 현재 터치 점 좌표 가 원 의 범위 에 떨 어 졌 는 지 에 따라 원 의 상 태 를 업데이트 하고 다시 그 릴 때 새로운 색 으로 그립 니 다.손가락 을 들 어 올 릴 때 저 장 된 상태의 list 를 원 의 list 를 선택 하고 linePath 를 초기 화하 고 결 과 를 되 돌려 줍 니 다.
private PointF touchPoint;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
reset();
case MotionEvent.ACTION_MOVE:
if (touchPoint == null) {
touchPoint = new PointF(event.getX(), event.getY());
} else {
touchPoint.set(event.getX(), event.getY());
}
for (int i = 0; i < rowCount * rowCount; i++) {
//
if (getDistance(touchPoint, points[i]) < radius) {
stateSparseArray.put(i, STATE_MOVE);
if (!selectedList.contains(points[i])) {
selectedList.add(points[i]);
}
break;
}
}
break;
case MotionEvent.ACTION_UP:
if (check()) { //
if (listener != null) {
listener.onComplete(true);
}
for (int i = 0; i < stateSparseArray.size(); i++) {
int index = stateSparseArray.keyAt(i);
stateSparseArray.put(index, STATE_MOVE);
}
} else { //
for (int i = 0; i < stateSparseArray.size(); i++) {
int index = stateSparseArray.keyAt(i);
stateSparseArray.put(index, STATE_ERROR);
}
linePaint.setColor(0xeeff0000);
if (listener != null) {
listener.onComplete(false);
}
}
touchPoint = null;
if (timer == null) {
timer = new Timer();
}
timer.schedule(new TimerTask() {
@Override
public void run() {
linePath.reset();
linePaint.setColor(0xee0000ff);
selectedList.clear();
stateSparseArray.clear();
postInvalidate();
}
}, 1000);
break;
}
invalidate();
return true;
}
각 원 과 각 원 사이 의 연결 선 을 그립 니 다.
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCircle(canvas);
drawLinePath(canvas);
}
private void drawCircle(Canvas canvas) {
// 0 8,
for (int index = 0; index < rowCount * rowCount; index++) {
int state = stateSparseArray.get(index);
switch (state) {
case STATE_NORMAL:
innerCirclePaint.setColor(normalColor);
outerCirclePaint.setColor(normalColor & 0x66ffffff);
break;
case STATE_MOVE:
innerCirclePaint.setColor(moveColor);
outerCirclePaint.setColor(moveColor & 0x66ffffff);
break;
case STATE_ERROR:
innerCirclePaint.setColor(errorColor);
outerCirclePaint.setColor(errorColor & 0x66ffffff);
break;
}
canvas.drawCircle(points[index].x, points[index].y, radius, outerCirclePaint);
canvas.drawCircle(points[index].x, points[index].y, radius / 2f, innerCirclePaint);
}
}
전체 보기 코드:
/**
* Created by star.tao on 2018/5/30.
* email: [email protected]
* github: https://github.com/xing16
*/
public class LockView extends View {
private static final int DEFAULT_NORMAL_COLOR = 0xee776666;
private static final int DEFAULT_MOVE_COLOR = 0xee0000ff;
private static final int DEFAULT_ERROR_COLOR = 0xeeff0000;
private static final int DEFAULT_ROW_COUNT = 3;
private static final int STATE_NORMAL = 0;
private static final int STATE_MOVE = 1;
private static final int STATE_ERROR = 2;
private int normalColor; //
private int moveColor; //
private int errorColor; //
private float radius; //
private int rowCount;
private PointF[] points; //
private Paint innerCirclePaint; //
private Paint outerCirclePaint; //
private SparseIntArray stateSparseArray;
private List<PointF> selectedList = new ArrayList<>();
private List<Integer> standardPointsIndexList = new ArrayList<>();
private Path linePath = new Path(); //
private Paint linePaint;
private Timer timer;
public LockView(Context context) {
this(context, null);
}
public LockView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
readAttrs(context, attrs);
init();
}
private void readAttrs(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LockView);
normalColor = typedArray.getColor(R.styleable.LockView_normalColor, DEFAULT_NORMAL_COLOR);
moveColor = typedArray.getColor(R.styleable.LockView_moveColor, DEFAULT_MOVE_COLOR);
errorColor = typedArray.getColor(R.styleable.LockView_errorColor, DEFAULT_ERROR_COLOR);
rowCount = typedArray.getInteger(R.styleable.LockView_rowCount, DEFAULT_ROW_COUNT);
typedArray.recycle();
}
private void init() {
stateSparseArray = new SparseIntArray(rowCount * rowCount);
points = new PointF[rowCount * rowCount];
innerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
innerCirclePaint.setStyle(Paint.Style.FILL);
outerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
outerCirclePaint.setStyle(Paint.Style.FILL);
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setStrokeCap(Paint.Cap.ROUND);
linePaint.setStrokeJoin(Paint.Join.ROUND);
linePaint.setStrokeWidth(30);
linePaint.setColor(moveColor);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// = = 2
radius = Math.min(w, h) / (2 * rowCount + rowCount - 1) * 1.0f;
//
for (int i = 0; i < rowCount * rowCount; i++) {
points[i] = new PointF(0, 0);
points[i].set((i % rowCount * 3 + 1) * radius, (i / rowCount * 3 + 1) * radius);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getSize(widthMeasureSpec);
int height = getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int getSize(int measureSpec) {
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
return size;
} else if (mode == MeasureSpec.AT_MOST) {
return Math.min(size, dp2Px(600));
}
return dp2Px(600);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCircle(canvas);
drawLinePath(canvas);
}
private void drawCircle(Canvas canvas) {
// 0 8,
for (int index = 0; index < rowCount * rowCount; index++) {
int state = stateSparseArray.get(index);
switch (state) {
case STATE_NORMAL:
innerCirclePaint.setColor(normalColor);
outerCirclePaint.setColor(normalColor & 0x66ffffff);
break;
case STATE_MOVE:
innerCirclePaint.setColor(moveColor);
outerCirclePaint.setColor(moveColor & 0x66ffffff);
break;
case STATE_ERROR:
innerCirclePaint.setColor(errorColor);
outerCirclePaint.setColor(errorColor & 0x66ffffff);
break;
}
canvas.drawCircle(points[index].x, points[index].y, radius, outerCirclePaint);
canvas.drawCircle(points[index].x, points[index].y, radius / 2f, innerCirclePaint);
}
}
/**
*
*
* @param canvas
*/
private void drawLinePath(Canvas canvas) {
// linePath
linePath.reset();
// 0 ,
if (selectedList.size() > 0) {
//
linePath.moveTo(selectedList.get(0).x, selectedList.get(0).y);
for (int i = 1; i < selectedList.size(); i++) {
linePath.lineTo(selectedList.get(i).x, selectedList.get(i).y);
}
// ,touchPoint null, , ,
if (touchPoint != null) {
linePath.lineTo(touchPoint.x, touchPoint.y);
}
canvas.drawPath(linePath, linePaint);
}
}
private PointF touchPoint;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
reset();
case MotionEvent.ACTION_MOVE:
if (touchPoint == null) {
touchPoint = new PointF(event.getX(), event.getY());
} else {
touchPoint.set(event.getX(), event.getY());
}
for (int i = 0; i < rowCount * rowCount; i++) {
//
if (getDistance(touchPoint, points[i]) < radius) {
stateSparseArray.put(i, STATE_MOVE);
if (!selectedList.contains(points[i])) {
selectedList.add(points[i]);
}
break;
}
}
break;
case MotionEvent.ACTION_UP:
if (check()) { //
if (listener != null) {
listener.onComplete(true);
}
for (int i = 0; i < stateSparseArray.size(); i++) {
int index = stateSparseArray.keyAt(i);
stateSparseArray.put(index, STATE_MOVE);
}
} else { //
for (int i = 0; i < stateSparseArray.size(); i++) {
int index = stateSparseArray.keyAt(i);
stateSparseArray.put(index, STATE_ERROR);
}
linePaint.setColor(0xeeff0000);
if (listener != null) {
listener.onComplete(false);
}
}
touchPoint = null;
if (timer == null) {
timer = new Timer();
}
timer.schedule(new TimerTask() {
@Override
public void run() {
linePath.reset();
linePaint.setColor(0xee0000ff);
selectedList.clear();
stateSparseArray.clear();
postInvalidate();
}
}, 1000);
break;
}
invalidate();
return true;
}
/**
* , invalidate()
*/
private void reset() {
touchPoint = null;
linePath.reset();
linePaint.setColor(0xee0000ff);
selectedList.clear();
stateSparseArray.clear();
}
public void onStop() {
timer.cancel();
}
private boolean check() {
if (selectedList.size() != standardPointsIndexList.size()) {
return false;
}
for (int i = 0; i < standardPointsIndexList.size(); i++) {
Integer index = standardPointsIndexList.get(i);
if (points[index] != selectedList.get(i)) {
return false;
}
}
return true;
}
public void setStandard(List<Integer> pointsList) {
if (pointsList == null) {
throw new IllegalArgumentException("standard points index can't null");
}
if (pointsList.size() > rowCount * rowCount) {
throw new IllegalArgumentException("standard points index list can't large to rowcount * columncount");
}
standardPointsIndexList = pointsList;
}
private OnDrawCompleteListener listener;
public void setOnDrawCompleteListener(OnDrawCompleteListener listener) {
this.listener = listener;
}
public interface OnDrawCompleteListener {
void onComplete(boolean isSuccess);
}
private float getDistance(PointF centerPoint, PointF downPoint) {
return (float) Math.sqrt(Math.pow(centerPoint.x - downPoint.x, 2) + Math.pow(centerPoint.y - downPoint.y, 2));
}
private int dp2Px(int dpValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
}
}
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Bitrise에서 배포 어플리케이션 설정 테스트하기이 글은 Bitrise 광고 달력의 23일째 글입니다. 자체 또는 당사 등에서 Bitrise 구축 서비스를 사용합니다. 그나저나 며칠 전 Bitrise User Group Meetup #3에서 아래 슬라이드를 발표했...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.